"""
Redis辅助工具
提供Redis操作和缓存功能
"""
import json
import pickle
import time
from functools import wraps
from flask import current_app


def get_redis_client():
    """
    获取Redis客户端实例
    
    返回:
        Redis客户端实例
    """
    if not hasattr(current_app, 'redis'):
        # 如果未配置redis客户端，返回None
        return None
    
    return current_app.redis


def redis_cache(key_pattern, expires=300):
    """
    Redis缓存装饰器
    
    参数:
        key_pattern: 缓存键模式，可以包含格式化参数
        expires: 过期时间（秒）
    
    返回:
        装饰器函数
    """
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            redis_client = get_redis_client()
            
            # 如果Redis未配置，直接执行函数
            if redis_client is None:
                return f(*args, **kwargs)
            
            # 生成缓存键
            cache_key = key_pattern
            
            # 如果key_pattern包含格式化参数，应用它们
            if '{' in key_pattern:
                # 合并位置参数和关键字参数用于格式化
                all_args = list(args)
                all_args.extend(kwargs.values())
                cache_key = key_pattern.format(*all_args, **kwargs)
            
            # 尝试从缓存获取结果
            cached = redis_client.get(cache_key)
            if cached:
                try:
                    return pickle.loads(cached)
                except (pickle.PickleError, TypeError):
                    # 如果反序列化失败，继续执行函数
                    pass
            
            # 执行原始函数
            result = f(*args, **kwargs)
            
            # 缓存结果
            try:
                redis_client.setex(
                    cache_key,
                    expires,
                    pickle.dumps(result)
                )
            except (pickle.PickleError, TypeError):
                # 如果序列化失败，记录错误但不阻止函数返回
                current_app.logger.warning(f"无法缓存结果到Redis: {cache_key}")
            
            return result
        return decorated_function
    return decorator


def clear_cache_pattern(pattern):
    """
    清除匹配模式的缓存
    
    参数:
        pattern: Redis键模式
    
    返回:
        已清除的键数量
    """
    redis_client = get_redis_client()
    
    if redis_client is None:
        return 0
    
    keys = redis_client.keys(pattern)
    
    if not keys:
        return 0
    
    return redis_client.delete(*keys)


def redis_rate_limit(key_pattern, limit=10, period=60):
    """
    Redis基于速率限制的装饰器
    
    参数:
        key_pattern: 限制键模式，可以包含格式化参数
        limit: 时间段内允许的请求数
        period: 时间段（秒）
    
    返回:
        装饰器函数
    """
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            redis_client = get_redis_client()
            
            # 如果Redis未配置，直接执行函数
            if redis_client is None:
                return f(*args, **kwargs)
            
            # 生成限制键
            rate_key = key_pattern
            
            # 如果key_pattern包含格式化参数，应用它们
            if '{' in key_pattern:
                all_args = list(args)
                all_args.extend(kwargs.values())
                rate_key = key_pattern.format(*all_args, **kwargs)
            
            # 获取当前计数并增加
            current = redis_client.get(rate_key)
            
            if current is not None and int(current) >= limit:
                # 达到限制
                return {
                    'status': 'error',
                    'message': f'请求频率超限，请在{period}秒后重试'
                }, 429
            
            # 使用管道确保原子操作
            pipe = redis_client.pipeline()
            
            if current is None:
                # 首次请求，设置计数为1并设置过期时间
                pipe.set(rate_key, 1)
                pipe.expire(rate_key, period)
            else:
                # 已有计数，增加
                pipe.incr(rate_key)
            
            pipe.execute()
            
            # 执行原始函数
            return f(*args, **kwargs)
        
        return decorated_function
    return decorator


def set_json_cache(key, data, expires=3600):
    """
    将JSON数据存入Redis缓存
    
    参数:
        key: 缓存键
        data: 要缓存的数据
        expires: 过期时间（秒）
    
    返回:
        是否成功缓存
    """
    redis_client = get_redis_client()
    
    if redis_client is None:
        return False
    
    try:
        # 将数据序列化为JSON
        serialized = json.dumps(data)
        redis_client.setex(key, expires, serialized)
        return True
    except Exception as e:
        current_app.logger.error(f"缓存JSON数据失败: {str(e)}")
        return False


def get_json_cache(key):
    """
    从Redis缓存中获取JSON数据
    
    参数:
        key: 缓存键
    
    返回:
        缓存的数据或None
    """
    redis_client = get_redis_client()
    
    if redis_client is None:
        return None
    
    try:
        data = redis_client.get(key)
        if data:
            return json.loads(data)
    except Exception as e:
        current_app.logger.error(f"获取缓存的JSON数据失败: {str(e)}")
    
    return None


def redis_lock(lock_name, timeout=10):
    """
    Redis分布式锁装饰器
    
    参数:
        lock_name: 锁的名称
        timeout: 锁的最大持有时间（秒）
    
    返回:
        装饰器函数
    """
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            redis_client = get_redis_client()
            
            # 如果Redis未配置，直接执行函数
            if redis_client is None:
                return f(*args, **kwargs)
            
            lock_id = f"{lock_name}:lock"
            lock_timeout = int(time.time()) + timeout + 1
            
            # 尝试获取锁
            acquired = redis_client.setnx(lock_id, lock_timeout)
            
            # 如果获取锁失败，检查是否已过期
            if not acquired:
                current_lock_timeout = redis_client.get(lock_id)
                
                if current_lock_timeout and int(current_lock_timeout) < int(time.time()):
                    # 锁已过期，获取新锁
                    old_timeout = redis_client.getset(lock_id, lock_timeout)
                    
                    # 确保没有竞争条件
                    if old_timeout and int(old_timeout) == int(current_lock_timeout):
                        acquired = True
            
            try:
                if acquired:
                    # 如果获取到锁，执行函数
                    return f(*args, **kwargs)
                else:
                    # 未获取到锁
                    current_app.logger.warning(f"无法获取锁: {lock_name}")
                    return {
                        'status': 'error',
                        'message': '操作正在进行中，请稍后重试'
                    }, 409
            finally:
                # 如果持有锁，释放锁
                if acquired:
                    redis_client.delete(lock_id)
        
        return decorated_function
    return decorator 